package com.itlong.cloud.utils.encrypt;

import com.itlong.cloud.utils.node.XMLParser;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.dom4j.DocumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.Collator;
import java.util.*;

/**
 * <desc>
 * md5加密帮助类
 * </desc>
 *
 * @createDate
 */
public class Md5EncryptUtil {
    private static final Logger LOG = LoggerFactory.getLogger(Md5EncryptUtil.class);

    /**
     * <desc>
     * MD5加密
     * </desc>
     *
     * @param str
     * @return MD5加密字符串
     * @author Jiaqi.X
     * @createDate 2017/09/15
     */
    public static String md5(String str) {
        return DigestUtils.md5Hex(str);
    }

    /**
     * <desc>
     * app签名MD5值
     * 规则：通过实体类属性名ascii码值顺序value+MD5加密串，返回MD5加密值
     * </desc>
     *
     * @param MOBILE_MD5_KEY md5加密串
     * @param model          dto传输模型
     * @return
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     * @throws java.lang.reflect.InvocationTargetException
     * @author Jiaqi.X
     * @createDate 2017/09/15
     */
    public static <T> boolean checkAppSign(T model, String appSign, String MOBILE_MD5_KEY) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        Class clazz = model.getClass();
        Field[] fields = clazz.getDeclaredFields();
        Arrays.sort(fields, new Comparator<Field>() {
            public int compare(Field o1, Field o2) {
                return Collator.getInstance(Locale.ENGLISH).compare(o1.getName().toLowerCase(), o2.getName().toLowerCase());
            }
        });
        StringBuilder sb = new StringBuilder();
        for (Field field : fields) {
            if (!field.getName().equals("token") && !field.getName().equals("createTime") && !field.getName().equals("language")) {
                sb.append(field.getName());
            }
        }
        //获取get属性方法名
        Object language;
        Method lm;
        try {
            lm = model.getClass().getMethod("getLanguage");
        } catch (Exception e) {
            lm = null;
        }
        if (lm != null) {
            language = lm.invoke(model);
            if (language != null) {
                sb.append(language);
            }
        }
        //获取get属性方法名
        Object value;
        Method m = model.getClass().getMethod("getCreateTime");
        value = m.invoke(model);
        sb.append(value);
        sb.append(MOBILE_MD5_KEY);
//        System.out.println("排序后Key结果----"+sb.toString());
        return md5(sb.toString()).equals(appSign);
    }

    /**
     * <desc>
     * app签名MD5值，使用参数value，而不是key
     * 规则：通过实体类属性名ascii码值顺序value+MD5加密串，返回MD5加密值
     * </desc>
     *
     * @param MOBILE_MD5_KEY md5加密串
     * @param model          dto传输模型
     * @return
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     * @throws java.lang.reflect.InvocationTargetException
     * @author wangzhi
     * @createDate 2019/09/19
     */
    public static <T> boolean checkAppSignValue(T model, String appSign, String MOBILE_MD5_KEY) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        Class clazz = model.getClass();
        Field[] fields = clazz.getDeclaredFields();
        Arrays.sort(fields, new Comparator<Field>() {
            public int compare(Field o1, Field o2) {
                return Collator.getInstance(Locale.ENGLISH).compare(o1.getName().toLowerCase(), o2.getName().toLowerCase());
            }
        });
        StringBuilder sb = new StringBuilder();
        for (Field field : fields) {
            if (!field.getName().equals("token") && !field.getName().equals("createTime") && !field.getName().equals("language")) {
                field.setAccessible(true);
                sb.append(field.get(model) == null ? "" : field.get(model));
            }
        }
        //获取get属性方法名
        Object language;
        Method lm;
        try {
            lm = model.getClass().getMethod("getLanguage");
        } catch (Exception e) {
            lm = null;
        }
        if (lm != null) {
            language = lm.invoke(model);
            if (language != null) {
                sb.append(language);
            }
        }
        //获取get属性方法名
        Object value;
        Method m = model.getClass().getMethod("getCreateTime");
        value = m.invoke(model);
        sb.append(value);
        sb.append(MOBILE_MD5_KEY);
        System.out.println("排序后Key结果----" + md5(sb.toString()));
        return md5(sb.toString()).equals(appSign);
    }

    /**
     * <desc>
     * APP端校验参数及签名串MD5值
     * </desc>
     *
     * @param params
     * @param appSign
     * @param MOBILE_MD5_KEY
     * @return
     */
    public static boolean checkAppSign(Map<String, String> params, String appSign, String MOBILE_MD5_KEY) {
        if (params == null || StringUtils.isBlank(appSign)) {
            return false;
        }
        StringBuffer stringA = new StringBuffer();
        List<Map.Entry<String, String>> mappingList = new ArrayList<Map.Entry<String, String>>(params.entrySet());
        Collections.sort(mappingList, new Comparator<Map.Entry<String, String>>() {
            @Override
            public int compare(Map.Entry<String, String> mappingF, Map.Entry<String, String> mappingS) {
                return mappingF.getKey().toLowerCase().compareTo(mappingS.getKey().toLowerCase());
            }
        });
        String createTimeValue = "", languageValue = "";
        for (Map.Entry<String, String> mapping : mappingList) {
            if ("createTime".equals(mapping.getKey())) {
                createTimeValue = mapping.getValue();
            } else if ("language".equals(mapping.getKey())) {
                languageValue = mapping.getValue();
            } else {
                stringA.append(mapping.getKey());
            }
        }
        stringA.append(languageValue);
        stringA.append(createTimeValue);

        String result = stringA.append(MOBILE_MD5_KEY).toString();
//        System.out.println("result:"+result);
        result = md5(result);
//        System.out.println("appSign:"+appSign);
        return result.equals(appSign);
    }

    /**
     * <desc>
     * APP端校验参数及签名串MD5值
     * </desc>
     *
     * @param params
     * @param appSign
     * @param MOBILE_MD5_KEY
     * @return
     */
    public static boolean checkAppSignValue(Map<String, String> params, String appSign, String MOBILE_MD5_KEY) {
        if (params == null || StringUtils.isBlank(appSign)) {
            return false;
        }
        StringBuffer stringA = new StringBuffer();
        List<Map.Entry<String, String>> mappingList = new ArrayList<Map.Entry<String, String>>(params.entrySet());
        Collections.sort(mappingList, new Comparator<Map.Entry<String, String>>() {
            @Override
            public int compare(Map.Entry<String, String> mappingF, Map.Entry<String, String> mappingS) {
                return mappingF.getKey().toLowerCase().compareTo(mappingS.getKey().toLowerCase());
            }
        });
        String createTimeValue = "", languageValue = "";
        for (Map.Entry<String, String> mapping : mappingList) {
            if ("createTime".equals(mapping.getKey())) {
                createTimeValue = mapping.getValue();
            } else if ("language".equals(mapping.getKey())) {
                languageValue = mapping.getValue();
            } else {
                stringA.append(mapping.getValue() == null ? "" : mapping.getValue());
            }
        }
        stringA.append(languageValue);
        stringA.append(createTimeValue);

        String result = stringA.append(MOBILE_MD5_KEY).toString();
//        System.out.println("result:"+result);
        result = md5(result);

//        System.out.println("appSign:"+appSign);
        return result.equals(appSign);

        // 兼容小程序未更新验签
//        return result.equals(appSign) || checkAppSign(params, appSign, MOBILE_MD5_KEY);
    }

    /**
     * <desc>
     * APP端校验参数及签名串MD5值
     * </desc>
     *
     * @param params         验签参数集
     * @param appSign        app端验签结果
     * @param MOBILE_MD5_KEY 验签key
     * @param mark           预留参数
     * @return
     */
    public static boolean checkAppSignValue(Map<String, Object> params, String appSign, String MOBILE_MD5_KEY, String mark) {
        if (params == null || StringUtils.isBlank(appSign)) {
            return false;
        }
        StringBuffer stringA = new StringBuffer();
        List<Map.Entry<String, Object>> mappingList = new ArrayList<Map.Entry<String, Object>>(params.entrySet());
        Collections.sort(mappingList, new Comparator<Map.Entry<String, Object>>() {
            @Override
            public int compare(Map.Entry<String, Object> mappingF, Map.Entry<String, Object> mappingS) {
                return mappingF.getKey().toLowerCase().compareTo(mappingS.getKey().toLowerCase());
            }
        });
        String createTimeValue = "", languageValue = "";
        for (Map.Entry<String, Object> mapping : mappingList) {
            if ("createTime".equals(mapping.getKey())) {
                createTimeValue = mapping.getValue() == null ? "" : mapping.getValue().toString();
            } else if ("language".equals(mapping.getKey())) {
                languageValue = mapping.getValue() == null ? "" : mapping.getValue().toString();
            } else {
                stringA.append(mapping.getValue() == null ? "" : mapping.getValue().toString());
            }
        }
        stringA.append(languageValue);
        stringA.append(createTimeValue);

        String result = stringA.append(MOBILE_MD5_KEY).toString();
        result = md5(result);
//        System.out.println("result:" + result);
//        System.out.println("appSign:" + appSign);
        return result.equals(appSign);
    }

    /**
     * <desc>
     * 云设备计算MD5
     * </desc>
     *
     * @param params
     * @param appsecret
     * @return
     */
    public static String cloudDeviceMd5(Map<String, Object> params, String createTime, String appsecret) {
        if (params == null || StringUtils.isBlank(appsecret)) {
            return null;
        }
        StringBuffer stringA = new StringBuffer();
        List<Map.Entry<String, Object>> mappingList = new ArrayList<Map.Entry<String, Object>>(params.entrySet());
        Collections.sort(mappingList, new Comparator<Map.Entry<String, Object>>() {
            @Override
            public int compare(Map.Entry<String, Object> mappingF, Map.Entry<String, Object> mappingS) {
                return mappingF.getKey().toLowerCase().compareTo(mappingS.getKey().toLowerCase());
            }
        });
        for (Map.Entry<String, Object> mapping : mappingList) {
            stringA.append(mapping.getValue() == null ? "" : mapping.getValue());
        }
        stringA.append(createTime);
        return md5(stringA.append(appsecret).toString());
    }

    /**
     * <desc>
     * 云电梯SDK校验参数及签名串MD5值
     * </desc>
     *
     * @param params
     * @param appSign
     * @param MOBILE_MD5_KEY
     * @return
     */
    public static boolean checkCloudTalkSDKSign(Map<String, Object> params, String appSign, String MOBILE_MD5_KEY) {
        if (params == null || StringUtils.isBlank(appSign)) {
            return false;
        }
        StringBuffer stringA = new StringBuffer();
        List<Map.Entry<String, Object>> mappingList = new ArrayList<Map.Entry<String, Object>>(params.entrySet());
        Collections.sort(mappingList, new Comparator<Map.Entry<String, Object>>() {
            @Override
            public int compare(Map.Entry<String, Object> mappingF, Map.Entry<String, Object> mappingS) {
                return mappingF.getKey().toLowerCase().compareTo(mappingS.getKey().toLowerCase());
            }
        });
        String createTimeValue = "";
        for (Map.Entry<String, Object> mapping : mappingList) {
            if ("createTime".equals(mapping.getKey())) {
                createTimeValue = mapping.getValue().toString();
            } else {
                stringA.append(mapping.getValue() == null ? "" : mapping.getValue().toString());
            }
        }
        stringA.append(createTimeValue);
        String result = stringA.append(MOBILE_MD5_KEY).toString();
        result = md5(result);
        return result.equals(appSign);
    }

    /**
     * <desc>
     * sdkV2版本验签方法
     * </desc>
     *
     * @param params
     * @param appSign
     * @param appsecret
     * @return
     */
    public static boolean checkSDKV2Sign(Map<String, Object> params, String appSign, String requestId, String timestamp, String appsecret) {
        if (params == null || StringUtils.isBlank(appSign)) {
            return false;
        }
        StringBuffer stringA = new StringBuffer();
        List<Map.Entry<String, Object>> mappingList = new ArrayList<Map.Entry<String, Object>>(params.entrySet());
        Collections.sort(mappingList, new Comparator<Map.Entry<String, Object>>() {
            @Override
            public int compare(Map.Entry<String, Object> mappingF, Map.Entry<String, Object> mappingS) {
                return mappingF.getKey().toLowerCase().compareTo(mappingS.getKey().toLowerCase());
            }
        });
        for (Map.Entry<String, Object> mapping : mappingList) {
            stringA.append(mapping.getValue() == null ? "" : mapping.getValue().toString());
        }
        stringA.append(requestId);
        stringA.append(timestamp);
        stringA.append(appsecret);
        String result = md5(stringA.toString());
        return result.equals(appSign);
    }


    /**
     * <desc>
     * sdkV3版本验签方法
     * </desc>
     *
     * @param params
     * @param appSign
     * @param appsecret
     * @return
     */
    public static boolean checkSDKV3Sign(Map<String, Object> params, String appsecret, String appSign, String... values) {
        String result = getSDKV3Sign(params, appsecret, values);
        if (StringUtils.isEmpty(result)) {
            return false;
        }
        return result.equals(appSign);
    }

    /**
     * <desc>
     * sdkv2计算MD5
     * </desc>
     *
     * @param params
     * @param appsecret
     * @return
     */
    public static String getSDKV2Md5(Map<String, Object> params, String requestId, String timestamp, String appsecret) {
        if (params == null || StringUtils.isBlank(appsecret)) {
            return null;
        }
        StringBuffer stringA = new StringBuffer();
        List<Map.Entry<String, Object>> mappingList = new ArrayList<Map.Entry<String, Object>>(params.entrySet());
        Collections.sort(mappingList, new Comparator<Map.Entry<String, Object>>() {
            @Override
            public int compare(Map.Entry<String, Object> mappingF, Map.Entry<String, Object> mappingS) {
                return mappingF.getKey().toLowerCase().compareTo(mappingS.getKey().toLowerCase());
            }
        });
        for (Map.Entry<String, Object> mapping : mappingList) {
            stringA.append(mapping.getValue() == null ? "" : mapping.getValue());
        }
        stringA.append(requestId);
        stringA.append(timestamp);
        stringA.append(appsecret);
        return md5(stringA.toString());
    }

    /**
     * <desc>
     * sdkv3计算sign
     * </desc>
     *
     * @param params
     * @param appsecret
     * @return
     */
    public static String getSDKV3Sign(Map<String, Object> params, String appsecret, String... values) {
        if (params == null || StringUtils.isBlank(appsecret)) {
            return null;
        }
        StringBuffer stringA = new StringBuffer();
        List<Map.Entry<String, Object>> mappingList = new ArrayList<Map.Entry<String, Object>>(params.entrySet());
        Collections.sort(mappingList, new Comparator<Map.Entry<String, Object>>() {
            @Override
            public int compare(Map.Entry<String, Object> mappingF, Map.Entry<String, Object> mappingS) {
                return mappingF.getKey().toLowerCase().compareTo(mappingS.getKey().toLowerCase());
            }
        });
        for (Map.Entry<String, Object> mapping : mappingList) {
            stringA.append(mapping.getValue() == null ? "" : mapping.getValue());
        }
        for (String value : values) {
            stringA.append(value);
        }
        stringA.append(appsecret);
        return md5(stringA.toString());
    }

    /**
     * <desc>
     * 将userId转化为8位的HEX(MD5加密后截取奇数位)
     * <desc/>
     *
     * @param source 待加密的MD5数据
     * @return 生成的HEX
     * @author Biao.R
     * @createDate 2018/1/24
     */
    public static String offLineUserIdHandler(String source) {
        if (StringUtils.isEmpty(source)) {
            return "";
        }
        if (source.length() <= 8) {
            return source;
        }
        StringBuffer sb = new StringBuffer(8);
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] array = md.digest(source.getBytes("utf-8"));
            //遍历获取4到12位，得到MD5的16位数，并取其中的奇数位
            for (int i = 4; i < 12; i++) {
                String substring = Integer.toHexString((array[i] & 0xFF) | 0x100).toUpperCase().substring(1, 3);
                sb.append(substring.charAt(0));
            }
        } catch (Exception e) {
            return null;
        }
        return sb.toString();
    }

    /**
     * <desc>
     * md5加密
     * </desc>
     *
     * @param psd
     * @param salt
     * @return
     */
    public static String enWithSalt(String psd, String salt) {
        try {
            StringBuffer stingBuffer = new StringBuffer();
            // 1.指定加密算法
            MessageDigest digest = MessageDigest.getInstance("MD5");
            // 2.将需要加密的字符串转化成byte类型的数据，然后进行哈希过程
            byte[] bs = digest.digest((psd + salt).getBytes());
            // 3.遍历bs,让其生成32位字符串，固定写法
            // 4.拼接字符串
            for (byte b : bs) {
                int i = b & 0xff;
                String hexString = Integer.toHexString(i);
                if (hexString.length() < 2) {
                    hexString = "0" + hexString;
                }
                stingBuffer.append(hexString);
            }
            return stingBuffer.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * <desc>
     * 微信支付签名算法
     * </desc>
     *
     * @param map 要参与签名的数据对象
     * @param key 参与加密的key
     * @return 签名
     * @throws IllegalAccessException
     * @author Jiaqi.X
     * @createDate 2018/04/18
     */
    public static String getWechatSign(Map<Object, Object> map, String key) {
        ArrayList<String> list = new ArrayList<String>();
        for (Map.Entry<Object, Object> entry : map.entrySet()) {
            if (entry.getValue() != "") {
                list.add(entry.getKey() + "=" + entry.getValue() + "&");
            }
        }
        int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            sb.append(arrayToSort[i]);
        }
        String result = sb.toString();
        result += "key=" + key;
        LOG.info("Sign Before MD5:" + result);
        result = md5(result).toUpperCase();
        LOG.info("Sign Result:" + result);
        return result;
    }


    /**
     * <desc>
     * 检验API返回的数据里面的签名是否合法，避免数据在传输的过程中被第三方篡改.
     * </desc>
     *
     * @param responseString API返回的XML数据字符串
     * @return API签名是否合法
     * @throws javax.xml.parsers.ParserConfigurationException
     * @throws java.io.IOException
     * @throws org.xml.sax.SAXException
     * @author Jiaqi.X
     * @createDate 2018/04/18
     */
    public static boolean checkWechatResponseSign(String responseString, String key) throws DocumentException {
//        Map<Object,Object> map = XMLParser.getMapFromXML(responseString, "xml");
        Map<Object, Object> map = XMLParser.xmlString2Map(responseString);
        String signFromAPIResponse = map.get("sign") == null ? null : map.get("sign").toString();
        if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {
            LOG.info("API返回的数据签名数据不存在，有可能被第三方篡改!!!");
            return false;
        }
        LOG.info("服务器回包里面的签名是:" + signFromAPIResponse);
        //清掉返回数据对象里面的Sign数据
        map.put("sign", "");
        //将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
        String signForAPIResponse = getWechatSign(map, key);
        if (!signForAPIResponse.equals(signFromAPIResponse)) {
            //签名验不过，表示这个API返回的数据有可能已经被篡改了
            LOG.info("API返回的数据签名验证不通过，有可能被第三方篡改!!!");
            return false;
        }
        LOG.info("恭喜，API返回的数据签名验证通过!!!");
        return true;
    }

    /**
     * <desc>
     * 获取派宝签名
     * </desc>
     *
     * @param params    加签字段
     * @param timeStamp 时间戳
     * @param appKey    派宝appkey
     * @param appToken  派宝apptoken
     * @return
     * @Author Zhu.yj
     * @createDate 2021/1/4
     */
    public static String getPadBotSign(Map<String, String> params, long timeStamp, String appKey, String appToken) throws Exception {

        //将map值按key升序排序
        List<Map.Entry<String, String>> mappingList = new ArrayList<>(params.entrySet());
        mappingList.sort(Comparator.comparing(mappingF -> mappingF.getKey().toLowerCase()));


        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : mappingList) {
            sb.append(entry.getKey()).append(":").append(entry.getValue()).append(",");
        }
        sb.append("time:").append(timeStamp).append(",appkey:")
                .append(appKey).append(",apptoken:").append(appToken);
        return Md5.En(sb.toString());
    }

}
